using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Lucene.Net.Documents;
using Lucene.Net.Search;
using NopSolutions.NopCommerce.BusinessLogic.Products;
using NopSolutions.NopCommerce.BusinessLogic.Products.Specs;
using NopSolutions.NopCommerce.Common.Utils;
using NopSolutions.NopCommerce.LuceneHelper.Queries;
using NopSolutions.NopCommerce.LuceneHelper.Tasks;

namespace NopSolutions.NopCommerce.LuceneHelper
{
    public static class Extensions
    {
        #region Products

        public static Document DocumentProduct(this Product product)
        {
            var minProductVariant = product.MinimalPriceProductVariant;
            if (minProductVariant != null)
            {
                var document = new Document();
                document.AddField("Const", "1", Field.Index.NOT_ANALYZED, Field.Store.NO)
                    .AddField("Name", product.Name)
                    .AddField("FullDescription", product.FullDescription, Field.Index.ANALYZED, Field.Store.NO)
                    .AddField("ProductID", product.ProductId.ToString(), Field.Index.NOT_ANALYZED)
                    .AddNumericField("SortOrder",
                                     minProductVariant.DisplayOrder == 0
                                         ? int.MaxValue
                                         : minProductVariant.DisplayOrder,
                                     Field.Store.NO, true)
                    .AddNumericField("Price", minProductVariant.Price, Field.Store.YES, true)
                    .AddNumericField("Rating",
                                     (product.TotalRatingVotes > 0)
                                         ? decimal.Divide(product.RatingSum, product.TotalRatingVotes)
                                         : 0M,
                                     Field.Store.NO, true)
                    .AddField("UpdatedOn", DateTime.SpecifyKind(product.UpdatedOn, DateTimeKind.Utc).ToString(new CultureInfo("en-US")));

                foreach (var manuId in product.ProductManufacturers.Select(x => x.ManufacturerId))
                {
                    document.AddField("ManufacturerID", manuId.ToString(), Field.Index.NOT_ANALYZED, Field.Store.NO);
                }

                foreach (var c in product.ProductCategories)
                {
                    var category = c.Category;
                    while (category != null)
                    {
                        document.AddField("CategoryID", category.CategoryId.ToString(), Field.Index.NOT_ANALYZED, Field.Store.NO);
                        category = category.ParentCategory;
                    }
                }
                var specIds = new List<int>();
                foreach (var productSpecOption in SpecificationAttributeManager.GetProductSpecificationAttributesByProductId(product.ProductId))
                {
                    if (productSpecOption.AllowFiltering)
                    {
                        var specId = productSpecOption.SpecificationAttribute.SpecificationAttributeId;
                        document.AddField("Spec" + specId,
                                          productSpecOption.SpecificationAttributeOptionId.ToString(),
                                          Field.Index.NOT_ANALYZED, Field.Store.NO);
                        if (!specIds.Contains(specId))
                            specIds.Add(specId);
                    }
                }
                //These following fields are only so we can find products that have an option of a certain specification
                foreach (var specId in specIds)
                {
                    document.AddField("Specification", specId.ToString(), Field.Index.NOT_ANALYZED, Field.Store.NO);
                }
                return document;
            }
            return null;
        }

        public static IList<Product> MapProducts(this IEnumerable<Document> documents, bool impersonate)
        {
            return documents.Select(document => MapProduct(document, impersonate)).ToList();
        }

        public static Product MapProduct(this Document document, bool impersonate)
        {
            var productId = document.GetValue("ProductID", 0);
            if (impersonate)
            {
                string name = document.GetValue("Name");
                decimal price = document.GetValue("Price", 0M);
                return new Product { ProductId = productId, SEName = name, Name = name };
            }
            return ProductManager.GetProductById(productId);
        }

        #endregion

        #region Specification options

        public static Document Document(this SpecificationAttributeOption specificationAttributeOption)
        {
            if (specificationAttributeOption == null)
                return null;
            var spec =
                SpecificationAttributeManager.GetSpecificationAttributeById(
                    specificationAttributeOption.SpecificationAttributeId);
            var document = spec.Document()
                .AddField(SpecificationOptionQuery.FIELDS_SPECIFICATION_ATTRIBUTE_OPTION_ID,
                          specificationAttributeOption.SpecificationAttributeOptionId.ToString())
                .AddField(SpecificationOptionQuery.FIELDS_SPECIFICATION_ATTRIBUTE_OPTION_NAME,
                          specificationAttributeOption.Name)
                .AddField(SpecificationOptionQuery.FIELDS_SPECIFICATION_ATTRIBUTE_OPTION_DISPLAYORDER,
                          specificationAttributeOption.DisplayOrder.ToString());
            return document;
        }

        public static SpecificationAttributeOption MapSpecificationOption(this Document document)
        {
            var specificationAttributeOption = new SpecificationAttributeOption();

            document.GetValue("SpecOptionID",
                              x => specificationAttributeOption.SpecificationAttributeOptionId = int.Parse(x))
                .GetValue("SpecOptionName", x => specificationAttributeOption.Name = x)
                .GetValue("SpecOptionDisplayOrder", x => specificationAttributeOption.DisplayOrder = int.Parse(x))
                .GetValue("SpecID", x => specificationAttributeOption.SpecificationAttributeId = int.Parse(x));

            return specificationAttributeOption;
        }

        public static IList<SpecificationAttributeOption> MapSpecificationOptions(this IEnumerable<Document> documents)
        {
            return documents.Select(MapSpecificationOption).ToList();
        }
 
        #endregion

        #region Specification attributes

        public static Document Document(this SpecificationAttribute specificationAttribute)
        {
            if (specificationAttribute == null)
                return null;
            var document = new Document()
                .AddField(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_ID, specificationAttribute.SpecificationAttributeId.ToString())
                .AddField(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_NAME, specificationAttribute.Name)
                .AddField(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_DISPLAYORDER, specificationAttribute.DisplayOrder.ToString());
            return document;
        }

        public static SpecificationAttribute MapSpecificationAttribute(this Document document)
        {
            var specificationAttribute = new SpecificationAttribute();
            document.GetValue(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_ID,
                              x => specificationAttribute.SpecificationAttributeId = int.Parse(x))
                .GetValue(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_NAME,
                          x => specificationAttribute.Name = x)
                .GetValue(SpecificationAttributeQuery.FIELDS_SPECIFICATION_ATTRIBUTE_DISPLAYORDER,
                          x => specificationAttribute.DisplayOrder = int.Parse(x));
            return specificationAttribute;
        }

        public static IList<SpecificationAttribute> MapSpecificationAttribute(this IEnumerable<Document> documents)
        {
            return documents.Select(MapSpecificationAttribute).ToList();
        }

        #endregion

        #region Misc

        public static string ToDelimitedString(this IEnumerable<PriceRange> values)
        {
            var stringValue = new StringBuilder();
            var sep = string.Empty;
            foreach (var range in values)
            {
                stringValue.Append(sep);
                sep = ";";
                if (range.From.HasValue)
                {
                    stringValue.Append(range.From.Value.ToString());
                    if (range.To.HasValue)
                        stringValue.Append("-");
                }
                if (range.To.HasValue)
                {
                    stringValue.Append(range.To.Value.ToString());
                }

            }
            return stringValue.ToString();
        }

        public static string ToDelimitedString(this IEnumerable<int> values)
        {
            var stringValue = new StringBuilder();
            var sep = string.Empty;
            foreach (var value in values)
            {
                stringValue.Append(sep);
                sep = ";";
                stringValue.Append(value.ToString());
            }
            return stringValue.ToString();
        }

        public static T ConvertTo<T>(this string value)
        {
            return (T)Convert.ChangeType(value, typeof(T), new CultureInfo("en-US"));
        }

        #endregion

        #region Url

        public static string RemoveQueryString(this string url, string query)
        {
            return CommonHelper.RemoveQueryString(url, query);
        }

        public static string ModifyQueryString(this string url, string query)
        {
            return CommonHelper.ModifyQueryString(url, query, null);
        }

        public static string ModifyQueryString(this string url, string key, string value)
        {
            return ModifyQueryString(url, key + "=" + value);
        }

        #endregion

        #region Document manipulation

        #region Adding

        public static Document AddField(this Document document, string name, string value)
        {
            return AddField(document, name, value, Field.Index.ANALYZED, Field.Store.YES);
        }

        public static Document AddField(this Document document, string name, string value, Field.Store store)
        {
            return AddField(document, name, value, Field.Index.ANALYZED, store);
        }

        public static Document AddField(this Document document, string name, string value, Field.Index index)
        {
            return AddField(document, name, value, index, Field.Store.YES);
        }

        public static Document AddNumericField<T>(this Document document, string name, T value, Field.Store store, bool index)
        {
            var field = new NumericField(name, store, index);
            if (typeof(T).Equals(typeof(int)))
            {
                field.SetIntValue(int.Parse(value.ToString(), new CultureInfo("en-US")));
            }
            else if (typeof(T).Equals(typeof(decimal)) || typeof(T).Equals(typeof(float)))
            {
                field.SetFloatValue(float.Parse(value.ToString(), new CultureInfo("en-US")));
            }
            else
            {
                throw new Exception("Not supported");
            }
            document.Add(field);
            return document;
        }

        public static Document AddField(this Document document, string name, string value, Field.Index index, Field.Store store)
        {
            if (document != null)
            {
                var field = new Field(name, value, Field.Store.YES, index);
                document.Add(field);
            }
            return document;
        }

        #endregion

        #region Getting

        public static string GetValue(this Document document, string field)
        {
            var values = document.GetValues(field);
            if (values.Any())
                return values[0];
            return "";
        }

        public static Document GetValue(this Document document, string field, Action<string> setter)
        {
            var value = GetValue(document, field);
            setter.Invoke(value);
            return document;
        }

        public static T GetValue<T>(this Document document, string field, T defaultValue)
        {
            return ConvertTo<T>(GetValue(document, field));
        }

        #endregion

        public static bool Exists(this Document document, string field, string value)
        {
            return document.GetValues(field).Any(v => v.Equals(value));
        }

        #endregion

        #region Tasks

        #region Products

        public static IList<ProductUpdateTask> ToTasks(this IList<Product> products)
        {
            return products.ToTasks(true);
        }

        public static IList<ProductUpdateTask> ToTasks(this IList<Product> products, bool forceUpdate)
        {
            return products.Select(product => new ProductUpdateTask(product.ProductId, forceUpdate)).ToList();
        }

        public static ProductUpdateTask ToTask(this Product product)
        {
            return product.ToTask(true);
        }

        public static ProductUpdateTask ToTask(this Product product, bool forceUpdate)
        {
            return new List<Product> { product }.ToTasks(forceUpdate).First();
        }

        #endregion

        #region Options

        public static IList<SpecificationOptionUpdateTask> ToTasks(this IList<SpecificationAttributeOption> options, bool updateProducts)
        {
            return options.Select(x => new SpecificationOptionUpdateTask(x, updateProducts)).ToList();
        }

        public static IList<SpecificationOptionUpdateTask> ToTasks(this IList<SpecificationAttributeOption> options)
        {
            return options.ToTasks(true);
        }

        public static SpecificationOptionUpdateTask ToTask(this SpecificationAttributeOption option, bool updateProducts)
        {
            return ToTasks(new List<SpecificationAttributeOption> {option}, updateProducts).First();
        }

        public static SpecificationOptionUpdateTask ToTask(this SpecificationAttributeOption option)
        {
            return option.ToTask(true);
        }

        #endregion

        #endregion

        //public static SpecificationAttributeOption MapSpecValue(this Document document)
        //{
        //    var option = new SpecificationAttributeOption();
        //    document.GetValue("SpecID", x => option.SpecificationAttributeId = int.Parse(x))
        //        .GetValue("SpecOptionID", x => option.SpecificationAttributeOptionId = int.Parse(x))
        //        .GetValue("SpecOptionName", x => option.Name = x)
        //        .GetValue("SpecOptionDisplayOrder", x => option.DisplayOrder = int.Parse(x));
        //    return option;
        //}
        //public static IList<SpecificationAttributeOption> MapSpecValues(this IEnumerable<Document> documents)
        //{
        //    return documents.Select(MapSpecValue).ToList();
        //}
        //public static IList<CategoryFilterValue> MapCategoryFilters(this IList<Document> documents)
        //{
        //    return documents.Select(document =>
        //                            new CategoryFilterValue(int.Parse(document.Get("CategoryID")), 0,
        //                                                    bool.Parse(document.Get("HasChildren")))).ToList();
        //}
        //public static IList<SpecificationFacet> FacetsGroup(this IEnumerable<SpecificationFacetValue> values)
        //{
        //    var groups = new List<SpecificationFacet>();
        //    foreach (SpecificationFacetValue value in values)
        //    {
        //        int specId = value.SpecificationId;
        //        if (groups.Any(x => x.SpecificationID == specId))
        //        {
        //            groups.Single(x => x.SpecificationID == specId).Values.Add(value);
        //        }
        //        else
        //        {
        //            var group = new SpecificationFacet(value.SpecificationName, value.SpecificationId,
        //                                               value.SpecDisplayOrder);
        //            group.Values.Add(value);
        //            groups.Add(group);
        //        }
        //    }
        //    return groups;
        //}
    }

    public static class QueryExtensions
    {
        public static Query QueryBoolean(this Query existing, Query query, BooleanClause.Occur occur)
        {
            if (query == null)
                return existing;
            if (existing == null)
                return query;
            var booleanQuery = new BooleanQuery();
            booleanQuery.Add(existing, occur);
            booleanQuery.Add(query, occur);
            booleanQuery.SetMinimumNumberShouldMatch(1);
            return booleanQuery;
        }
        public static Query QueryAnd(this Query existing, Query query)
        {
            return QueryBoolean(existing, query, BooleanClause.Occur.MUST);
        }
        public static Query QueryOr(this Query existing, Query query)
        {
            return QueryBoolean(existing, query, BooleanClause.Occur.SHOULD);
        }
    }
}